package cn.zb.zbrhback.auth.service.impl;

import cn.zb.zbrhback.auth.annotation.ZBPermission;
import cn.zb.zbrhback.auth.domain.Permission;
import cn.zb.zbrhback.auth.mapper.PermissionMapper;
import cn.zb.zbrhback.auth.service.IPermissionScanService;
import cn.zb.zbrhback.basic.util.ClassUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

@Service
public class PermissionScanServiceImpl implements IPermissionScanService {

    @Value("${permission.scan-base-package}")
    private String scanBasePackage;

    @Resource
    private PermissionMapper permissionMapper;

    @Transactional
    @Override
    public void scan() {
        // 扫描所有controller包下面打了@ZBPermission的类和方法
        // 1 获取特定包下面所有类的Class字节码文件 controller这个包下面的类
        List<Class> clazzs = ClassUtils.getAllClassName(this.scanBasePackage);
        List<Permission> permissions = new ArrayList<>();

        if(clazzs != null && clazzs.size() > 0){
            // 2 循环拿到的所有的controller的字节码文件
            for (Class clazz : clazzs) {
                // 2.1 判断类上是否有@ZBPermission注解,如果没有就直接跳过
                ZBPermission clazzAnnotation = (ZBPermission) clazz.getAnnotation(ZBPermission.class);
                if(Objects.isNull(clazzAnnotation)){
                    continue;
                }
                // 获取类上面的url路径
                RequestMapping requestMapping = (RequestMapping)clazz.getAnnotation(RequestMapping.class);
                String clazzUrl = requestMapping.value()[0];

                // 将类上面的@ZBPermisson注解解析为一个Permission对象
                Permission parent = new Permission();
                parent.setName(clazzAnnotation.name());
                parent.setDescs(clazzAnnotation.desc());
                parent.setUrl(clazzUrl);
                parent.setSn(clazz.getSimpleName());
                permissions.add(parent);

                // 2.2 如果类上有注解,获取类里面所有的方法
                Method[] methods = clazz.getMethods();
                // 3 遍历所有的方法,判断方法上是否有@RonghuaPermission
                for (Method method : methods) {
                    // 3.1 如果没有这个注解,就说明当前方法不需要权限就可以直接访问了,所以就跳过不处理
                    ZBPermission ronghuaPermission = method.getAnnotation(ZBPermission.class);
                    if(Objects.isNull(ronghuaPermission)){
                        continue;
                    }
                    // 3.2 如果有的话,就要获取name desc 要拼接sn url 将它们封装成一个Permission对象
                    Permission permission = new Permission();
                    permission.setName(ronghuaPermission.name());
                    permission.setDescs(ronghuaPermission.desc());
                    // 处理sn  类名:方法名
                    String sn = clazz.getSimpleName()+":"+method.getName();
                    permission.setSn(sn);
                    // 处理url  url指的是前端在访问时需要访问的url,所以它是类上面的@RequestMapping的值+方法上面的那个url
                    String methodUrl = getMethodUrl(method);
                    permission.setUrl(clazzUrl+methodUrl);
                    permission.setParent(parent);   // 以类上面解析的permission对象作为父权限对象
                    permissions.add(permission);
                }
            }
        }
        // 4 将Permission对象存到数据库
        // 问题:每次启动项目,都要来扫描一次,并添加到数据库,这样子的话是不合理的
        // 解决方案:方案一: 在新增时 根据sn判断一下这个权限在数据库是否存在,如果已经存在就不添加了 如果不存在再添加
        //               问题: 这个权限如果之前是有的,但是现在方法上面的注解删掉了,这样的话 就不好处理这种情况
        //               要先查询出数据库里面所有的权限,然后再和刚刚封装好的权限对象做比较,如果数据库有,而封装后的没有,就要将数据库的那个权限删除掉
        // 方案二: 直接先删除所有权限,再全部 全量新增
        permissionMapper.removeAll();
        for (Permission permission : permissions) {
            permissionMapper.insert(permission);
        }
    }

    /**
     * 获取方法上面的url
     * @param method
     * @return
     */
    private String getMethodUrl(Method method) {
        String methodUrl = "";
        PutMapping putMapping = method.getAnnotation(PutMapping.class);
        if(Objects.nonNull(putMapping)){
            methodUrl = putMapping.value() != null && putMapping.value().length > 0 ? putMapping.value()[0] : "";
        }
        PostMapping postMapping = method.getAnnotation(PostMapping.class);
        if(Objects.nonNull(postMapping)){
            methodUrl = postMapping.value() != null && postMapping.value().length > 0 ? postMapping.value()[0] : "";
        }
        GetMapping getMapping = method.getAnnotation(GetMapping.class);
        if(Objects.nonNull(getMapping)){
            methodUrl = getMapping.value() != null && getMapping.value().length > 0? getMapping.value()[0] : "";
        }
        DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
        if(Objects.nonNull(deleteMapping)){
            methodUrl = deleteMapping.value() != null && deleteMapping.value().length > 0 ? deleteMapping.value()[0] : "";
        }
        PatchMapping patchMapping = method.getAnnotation(PatchMapping.class);
        if(Objects.nonNull(patchMapping)){
            methodUrl = patchMapping.value() != null && patchMapping.value().length > 0 ? patchMapping.value()[0] : "";
        }
        return methodUrl;
    }
}
